package cn.yeziji.forum.common.aspcet;

import cn.yeziji.forum.annoataion.RedisLock;
import cn.yeziji.forum.common.ForumResult;
import cn.yeziji.forum.exception.ForumException;
import cn.yeziji.forum.utils.ForumResultUtils;
import cn.yeziji.forum.utils.RedisLockUtils;
import cn.yeziji.forum.utils.UserOnlineUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.StringJoiner;

/**
 * @author hwy
 * @since 2023/06/20 14:30
 **/
@Slf4j
@Aspect
@Component
public class RedisLockAspect {
  @Resource
  private RedisLockUtils redisLockUtils;

  @Around("@annotation(redisLock)")
  public Object around(ProceedingJoinPoint pjp, RedisLock redisLock) throws Throwable {
    Method method = ((MethodSignature) pjp.getSignature()).getMethod();
    String key = getKey(method, redisLock, pjp);

    int retryTimes = redisLock.action().equals(RedisLock.LockFailAction.CONTINUE) ? redisLock.retryTimes() : 0;
    boolean lock = redisLockUtils.lock(key, redisLock.keepMills(), retryTimes, redisLock.sleepMills());
    log.info("线程{}获取分布式锁{},获取结果:{}", Thread.currentThread().getId(), key, (lock ? " success" : " failed"));

    if (!lock) {
      Class<?> returnType = method.getReturnType();
      if (returnType == ForumResult.class) {
          return ForumResultUtils.error(redisLock.failMsg());
      } else {
        return null;
      }
    }
    //得到锁,执行方法，释放锁
    try {
      return pjp.proceed();
    } catch (ForumException e) {
      log.warn("执行分布式锁方法异常", e);
      throw e;
    } catch (Throwable e) {
      log.error("执行分布式锁方法异常", e);
      throw e;
    } finally {
      boolean releaseResult = redisLockUtils.releaseLock(key);
      log.info("线程{}释放分布式锁{}，释放结果:{}", Thread.currentThread().getId(), key, (releaseResult ? " success" : " failed"));
    }
  }

  private String getKey(Method method, RedisLock redisLock, ProceedingJoinPoint pjp) throws Exception {
    StringJoiner keyJoiner = new StringJoiner("_");
    if (redisLock.concatTokenMsg() && StringUtils.isNotBlank(UserOnlineUtils.getToken())) {
      keyJoiner.add(UserOnlineUtils.getUserId() + "-" + UserOnlineUtils.getUserName());
    }
    if (StringUtils.isNotBlank(redisLock.lockKey())) {
      if (redisLock.useKeyPrefix()) {
        keyJoiner.add(redisLock.lockKey()).add(method.getName());
      } else {
        keyJoiner.add(method.getName());
      }
    }

    if (redisLock.concatArgs()) {
      int indexOfArgs = redisLock.indexOfArgs();
      String fieldName = redisLock.fieldName();
      Object[] args = pjp.getArgs();
      // 下标越界校验
      if (indexOfArgs > -1 && indexOfArgs < args.length) {
        Object targetArgs = args[indexOfArgs];
        // 入参非空校验
        if (targetArgs != null && "" != targetArgs) {
          if (redisLock.isObject()) {
            // 入参是一个对象 从对象中取值作为key
            if (StringUtils.isNotBlank(fieldName)) {
              Class<?> targetArgsClass = targetArgs.getClass();
              Field targetArgsClassDeclaredField = targetArgsClass.getDeclaredField(fieldName);
              targetArgsClassDeclaredField.setAccessible(true);
              Object targetFieldValue = targetArgsClassDeclaredField.get(targetArgs);
              // 反射获取的属性值 非空校验
              if (targetFieldValue == null || "".equals(targetFieldValue)) {
                log.error("分布式锁拼接key不能为空 {}", args);
              } else {
                keyJoiner.add(targetFieldValue.toString());
              }
            }
          } else {
            // 入参直接取值作为key
            keyJoiner.add(targetArgs.toString());
          }
        } else {
          log.error("分布式锁拼接key不能为空 {}", args);
        }
      }
    }
    return keyJoiner.toString();
  }
}
